#! /usr/bin/env python
# -*- coding:utf-8 -*-
import model_strategic as sa
import run_manual as rl
import datetime
import model_hour as hp
import numpy as np
import utils_data as util
import os
import model_condition as sc
import load_info as load
import pandas as pd
import logging
logging.basicConfig(level=logging.DEBUG, #日志级别为DEBUG
                    format="%(asctime)s %(pathname)s %(levelname)s %(lineno)d %(message)s", #日志格式设置
                    datefmt = '%Y-%m-%d  %H:%M:%S %a',    #日期格式设置
                    filename = os.getcwd()+'/common/logging.log')  #日志输入文件地址
"""
每小时自动执行脚本 
主要功能：预测能量、电量、策略、合并策略、预测小时需求
结构：
    class auto #自动执行类
    def main 执行函数
前置条件：
    17时：预测日在数据库中有天气信息
          一个dev_code下model表有且仅有一个is_used为1的模型
    非17时：pre_data_day中存在预测日的能量电量预测结果
           strategy_output表中存在预测日的策略
           预测日在数据库中有天气信息
           一个dev_code下model表有且仅有一个is_used为1的模型
运行方式： 直接运行main()    
"""

class auto:
    """
    自动执行能源路由器
    """
    def __init__(self,today,stgy_type=None):
        self.today = today  #datetime 当前时间
        ld = load.Device_load()
        lg = load.Graph_load()
        self.dev_code_ene =ld.load_station_info()['energy_code']
        self.dev_code_ele = ld.load_station_info()['battery_code']
        self.date = ld.load_pre_date(self.today) #datetime 预测日
        graph_id = lg.put_databydate(self.date.strftime("%Y-%m-%d"))  # 获取target对应日期的图谱ID
        if stgy_type is None:
            self.stgy_type = lg.load_strategy_type(graph_id)
        else:
            self.stgy_type = stgy_type

    #数据集生成
    def do_dataset(self, dev_code,model_id, start_time=None, end_time=None,data_path = None):
        """
        生成数据集
        :param dev_code: str,设备id
        :param model_id: int,模型版本
        :param start_time: datetime,数据集开始时间
        :param end_time:  datetime,数据集结束时间
        :param data_path: str,数据集地址全路径
        :return: data_path：str,数据集地址全路径
        """
        if dev_code == None:  #设备code默认能量
            dev_code = self.dev_code_ene
        if start_time == None:  #默认开始结束时间是预测日
            start_time = self.date
            end_time = self.date
        #1.根据输入的开始结束时间和dev_code生成数据集
        ul = util.Dataset_util(dev_code)
        X, Y = ul.build_data(start_time, end_time, dev_code)  #获取数据集和标签
        #2.有拼接文件的情况下,拼接数据集
        ld = load.Device_load()
        model_info= ld.load_model_info(int(model_id))
        isjoin = model_info["isjoin"].values[0]
        if isjoin:
            base_path = os.path.dirname(__file__)
            joinpath = ld.load_model_info(int(model_id))["join_path"].values[0] #模型信息
            joinpath = base_path +os.sep+joinpath
            join_df = pd.read_csv(joinpath,encoding="utf-8")
            join_df['sdate'] = pd.to_datetime(join_df['sdate']).dt.date
            X = pd.merge(X, join_df, how='left', on='sdate')
        #3.拼接数据集与标签,并输出到csv
        X["data"] = Y
        if data_path ==None:
            data_path = os.getcwd()+r"/test/test_%s/%s.csv"%(dev_code,self.today.strftime("%Y%m%d%H%M"))
        if not os.path.exists(os.path.abspath(os.path.join(data_path,".."))):
            os.mkdir(os.path.abspath(os.path.join(data_path,"..")))
        X.to_csv(data_path, encoding="utf-8", index=False)
        return data_path
    #预测小时模型
    def pre_hour(self,strat_dict):
        """
        预测能量、电量、策略的小时趋势，存储为输出到数据库格式的二维列表格式
        :param:strat_dict:dict,策略字典(from_device_id,to_device_id):[...]
        :return:
            list:[[data,sdate,device_code,graph_id,stgy_type],......,[]]
        """
        #获取能量、电量日预测值
        ld = load.Device_load()
        la = load.Data_load()
        model_ene_id, model_ele_id = ld.load_model_id()
        daily_ele, _, _ = la.load_pre_day(self.dev_code_ele,int(model_ele_id))  # 电量日预测,模型版本电量,图谱ID电量
        #预测能量、电量、策略小时曲线
        ph = hp.predict_hour(self.date)
        ene_hour = ph.energy_hour(strat_dict)
        ele_hour = ph.electric_hour(strat_dict, int(daily_ele))
        strate_hour = ph.strate_hour(strat_dict)
        return ene_hour,ele_hour,strate_hour
    #小时预测结果转换为输出到predict_data和predict_dl表的列表格式
    def pre_hour_to_list(self, ene_hour, ele_hour):
        """
        小时预测结果转换为输出到predict_data和predict_dl表的列表格式
        :param ene_hour: list,[...] 能量小时预测结果
        :param ele_hour:  list,[...] 电量小时预测结果
        :return: ene_list：list  [str, date, graph_id, stgy_type, model_version]
                 ele_list：list  [str, date, graph_id, stgy_type, model_version]
        """
        # 获取能量、电量日预测值
        ld = load.Device_load()
        la = load.Data_load()
        model_ene_id, model_ele_id = ld.load_model_id()
        daily_ene, model_version, graph_id = la.load_pre_day(self.dev_code_ene, int(model_ene_id))  # 能量日预测,模型版本能量,图谱ID能量
        if len(ene_hour) != 24:
            logging.error('能量预测结果小时数不符')
            raise IndexError('能量数据数量不符，写入predict_data失败')
        if len(ele_hour) != 24:
            logging.error('电量预测结果小时数不符')
            raise IndexError('电量数据数量不符，写入predict_dl失败')
        ene_list = [str(ene_hour)[1:-1], self.date, int(graph_id), int(self.stgy_type), int(model_version)]
        ele_hour = [round(i,1) for i in ele_hour]  # 结果保留一位小数
        ele_list = [str(ele_hour)[1:-1], self.date, int(graph_id), int(self.stgy_type), int(model_ele_id)]
        return ene_list, ele_list
    #将预测小时list转换为输出到数据库的list格式并合并
    def pre_hour_to_comlist(self,ene_hour, ele_hour, strate_hour):
        """
        将预测能量电量工况格式化
        :param ene_hour: list:[...]
        :param ele_hour: list:[...]
        :param strate_hour:dict,(from_device_id,to_device_id):[...]
        :return:
            list:[[data,sdate,device_code,graph_id,stgy_type],......,[]]
        """
        #将预测结果合并为一个list输出
        ph = hp.predict_hour(self.date)
        ene_list = ph.hour_to_list(ene_hour, self.date, self.dev_code_ene)
        ele_list = ph.hour_to_list(ele_hour, self.date, self.dev_code_ele)
        list_com = ene_list + ele_list
        if len(strate_hour)>0:
            for key in strate_hour.keys():
                strate_list = ph.hour_to_list(strate_hour[key], self.date, key)
                list_com += strate_list
        return list_com
    #17时（下班时间）运行函数
    def hour_start(self):
        """
        当现在是17(下班时间)时
                    1 预测能量电量模型 写入pre_data_day表,
                    2 策略模型 写入strategy_output和strategy_temp表和history_data表,
                    3 小时模型并将结果写入pre_data_hour predict_dl predict_data表
        """
        dict_res = {}
        lode = load.Device_load()
        to = util.To_database()
        loda = load.Data_load()
        lg = load.Graph_load()
        lg.put_databydate(self.date)
        lu = util.Device_util(int(lg.graph_id),None,self.stgy_type)

        model_ene_id, model_ele_id = lode.load_model_id()       #获取模型id
        data_path_ene = self.do_dataset(self.dev_code_ene,int(model_ene_id))     #生成数据集
        data_path_ele = self.do_dataset(self.dev_code_ele,int(model_ele_id))     #生成数据集
        er_ene = rl.manual(model_ene_id,data_path_ene)  #调用
        er_ele = rl.manual(model_ele_id,data_path_ele)  #调用

        #预测能量电量,写入pre_data_day表
        pre_ene = er_ene.do_test()#预测能量
        #根据前日室内舒适性类型 更新能量值
        comf_type = lu.comfort_type(self.dev_code_ene, self.date) #前日室内温度舒适类型
        pre_data_update = []
        for pre_i in pre_ene["data"].values: #预测能量值数组
            update_i = lu.update_energy(comf_type, pre_i, update_rate=0.1)
            pre_data_update.append(update_i)
        pre_ene["date"] = pre_data_update

        pre_ele = er_ele.do_test()       #预测电量
        to.to_pre_data_day(self.dev_code_ene, pre_ene,int(model_ene_id))  # 预测能量需求且写入数据库pre_data_day
        to.to_pre_data_day(self.dev_code_ele, pre_ele,int(model_ele_id))  # 预测电量需求且写入数据库pre_data_day
        logging.info("progress 1/8 --> To predict the energy/electric and write it to the pre_data_day energy pre= %s ,electric pre = %s!", pre_ene["data"].values,pre_ele["data"].values)

        #读取模型版本、图谱ID及图谱下工况list
        daily_ene, model_version, graph_id = loda.load_pre_day(self.dev_code_ene,int(model_ene_id))  #能量日预测,模型版本能量,图谱ID能量
        relation_list = util.Device_util(int(lg.graph_id), self.date,self.stgy_type).relation_split()
        logging.info("progress 2/8 --> To load relation_list , pre relation = %s ,Condition relation = %s!", relation_list[0]["rel_name"].values,relation_list[1]["rel_name"].values)

        #如果当前图谱下有预测工况
        if len(relation_list[0]): #若预测工况表非空（过渡季），进行预测
            #预测策略,生成策略字典
            sm = sa.Strategic_model(int(daily_ene), self.date,self.stgy_type)
            strat_matrix = sm.strate_model()
            strat_dict = sm.strate_format(strat_matrix)  # 执行策略模型的主函数
            #如果当前图谱下有固定工况,将固定工况字典与预测工况字典进行合并
            if len(relation_list[1]):  # 若工况表非空（过渡季），进行预测
                relation = sc.RelationCondition(self.date)  # 加载固定工况计算类
                dict_not_suggest = relation.get_data_dispredict()  # TODO
                dict_res.update(dict_not_suggest)
                con_dict = relation.get_data()  # 预测的固定工况字典
                strat_dict.update(con_dict)
            new_list,strat_mean = util.Device_util(graph_id,None,self.stgy_type).strategy_to_list_mean(strat_dict, model_version, self.date)
            to.to_temp(new_list)  # 将策略写入strategy_temp表
            logging.info("progress 3/8 --> to_temp down!")
            #将策略字典转换为list并输出到temp,pre_strategy,output
            dict_res.update(strat_mean)
            strat_list = [i[:-2] for i in new_list]
            to.to_output(strat_list,self.date,self.stgy_type)  # 将策略写入strategy_output表
            logging.info("progress 4/8 --> to_output down!")
            #预测小时模型,输出到hour表,predict_data,predict_dl
            hour_data = self.pre_hour(strat_mean)  # 预测小时趋势 [ene, ele, stg]
            to.to_hour(self.pre_hour_to_comlist(*hour_data),self.date)  # 将结果转换为输出到数据库的格式,并将小时模型预测结果输出到数据库
            logging.info("progress 5/8 --> to_hour down!")
            ene_data,ele_data = self.pre_hour_to_list(hour_data[0],hour_data[1])
            to.to_predict_data(ene_data,self.stgy_type)  # 写入能量predict_data
            logging.info("progress 6/8 --> to_predict_data down!")
            to.to_predict_dl(ele_data,self.stgy_type)  # 写入电量predict_dl
            logging.info("progress 7/8 --> to_predict_dl down!")
        else:#如果当前图谱下没有预测工况
            if len(relation_list[1]): #如果当前图谱下有固定工况
                #预测固定工况,得到固定工况字典
                relation = sc.RelationCondition(self.date)  # 加载固定工况计算类
                dict_not_suggest = relation.get_data_dispredict()  # TODO
                dict_res.update(dict_not_suggest)
                con_dict = relation.get_data()  # 预测的固定工况字典
                con_list,_ = util.Device_util(graph_id,None,self.stgy_type).strategy_to_list_mean(con_dict, model_version, self.date)
                to.to_temp(con_list)  # 将策略写入strategy_temp表
                logging.info("progress 3/8 --> to_temp down!")
                #将固定工况字典转换为list并输出到temp,pre_strategy,output表
                dict_res.update(con_dict)
                strat_list = [i[:-2] for i in con_list]
                to.to_output(strat_list, self.date,self.stgy_type)  # 将策略写入strategy_output表
                logging.info("progress 4/8 --> to_output down!")
                #预测小时模型,输出到hour表,predict_data,predict_dl
                hour_data = self.pre_hour(con_dict)  # 预测小时趋势 [ene, ele, stg]
                to.to_hour(self.pre_hour_to_comlist(*hour_data),self.date)  # 将结果转换为输出到数据库的格式,并将小时模型预测结果输出到数据库
                logging.info("progress 5/8 --> to_hour down!")
                ene_data, ele_data = self.pre_hour_to_list(hour_data[0], hour_data[1])
                to.to_predict_data(ene_data,self.stgy_type)  # 写入能量predict_data
                logging.info("progress 6/8 --> to_predict_data down!")
                to.to_predict_dl(ele_data,self.stgy_type)  # 写入电量predict_dl
                logging.info("progress 7/8 --> to_predict_dl down!")
            else:
                #生成无策略建议工况，入库
                strat_list = [[self.date.replace(hour=0, minute=0, second=0, microsecond=0),
                              self.date.replace(hour=23, minute=59, second=59, microsecond=0), 0, 0, 0, '无策略建议', lg.graph_id,
                              self.stgy_type, model_version]]
                to.to_output(strat_list,self.date,self.stgy_type)
                logging.info("progress 3~7/8 --> to_output down!")

        if dict_res:
            to.to_history(int(self.stgy_type), dict_res, int(graph_id),self.date)  #将策略对应设备写入history_data表
            logging.info("progress 8/8 --> to_history down!")
        else:
            logging.info("progress 8/8 --> no history down!")
    #非17时（非下班时间）的运行函数
    def hour_non_start(self):
        """
        当现在是非17时（非下班时间及前一个小时）
            1 读取output表真实策略
            2 读取temp表升级策略（如果create_time大于等于now-1小时）
            3 预测新策略,写入temp表        （如果能量值变化率大于0.1）
            4 预测固定策略,写入temp表       （如果固定策略与output中的固定策略比有变化）
            4 预测能量/电量需求值
            5 预测电量小时模型,写入predict_dl,写入pre_data_day   （如果电量值变化率大于0.1）
            6 预测能量小时模型,写入predict_data,写入pre_data_day   （如果能量值变化率大于0.1）
            7 合并1,2,3,4,输出到output表
        """
        #加载参数
        lode = load.Device_load()
        gp = load.Graph_load()
        graph_id = gp.put_databydate(self.date.strftime("%Y-%m-%d"))  # 获取target对应日期的图谱ID
        ul = util.Device_util(graph_id,self.date,self.stgy_type)
        la = load.Data_load(self.date)
        to = util.To_database()
        #生成能量电量数据集/获取当前图谱下工况list
        model_ene_id, model_ele_id = lode.load_model_id()  # 获取默认模型id #TODU
        data_path_ene = self.do_dataset(self.dev_code_ene,int(model_ene_id))
        data_path_ele = self.do_dataset(self.dev_code_ele,int(model_ele_id))
        er_ene = rl.manual(model_ene_id, data_path_ene)
        er_ele =  rl.manual(model_ele_id, data_path_ele)
        relation_list = util.Device_util(gp.graph_id, self.date,self.stgy_type).relation_split()  # 工况表 [[模型],[固定]]
        logging.info("progress 1/7 --> To load relation_list , pre relation = %s ,Condition relation = %s!", relation_list[0]["rel_name"].values,relation_list[1]["rel_name"].values)

        #计算能量当前预测值与前次预测值差异率
        bef_ene, model_version_ene, graph_id = la.load_pre_day(self.dev_code_ene, int(model_ene_id))  # 获取能量小时预测值-上一次的
        bef_ene = bef_ene if bef_ene >0 else 1 #避免除0
        pre_ene = er_ene.do_test()  # 预测能量需求
        #根据前日室内舒适性类型 更新能量值
        comf_type = ul.comfort_type(self.dev_code_ene, self.date)  # 前日室内温度舒适类型
        pre_data_update = []
        for pre_i in pre_ene["data"].values:  # 预测能量值数组
            print("***comf_type:",comf_type)
            update_i = ul.update_energy(comf_type, pre_i, update_rate=0.1)
            print("***update_i:",update_i)
            pre_data_update.append(update_i)
        pre_ene["date"] = pre_data_update

        change_rate_ene = np.abs(bef_ene - pre_ene["data"].values[0]) / bef_ene  # 能量预测值的变化率
        #计算电量当前预测值与前次预测值差异率
        bef_ele, model_version_ele, graph_id = la.load_pre_day(self.dev_code_ele, int(model_ele_id))  # 获取能量小时预测值-上一次的
        bef_ele = bef_ele if bef_ele >0 else 1 #避免除0
        pre_ele = er_ele.do_test()  # 预测能量需求
        change_rate_ele = np.abs(bef_ele - pre_ele["data"].values[0]) / bef_ele  # 能量预测值的变化率
        logging.info("progress 2/7 --> To load chanege rate , change_rate_ene = %s ,change_rate_ele = %s!", change_rate_ene,change_rate_ele)

        #如果当前图谱下有预测工况
        if len(relation_list[0]):
            #加载真实工况和升级工况,得到真实工况字典和升级工况df
            df_actual = lode.load_actual(self.stgy_type,self.date)  # 获取output表的真实策略
            dict_actual = ul.strategy_to_dict(df_actual)
            df_upgrade, status_upgrade = lode.load_upgrade()  # 获取升级策略
            #预测固定策略,得到固定工况字典
            relation = sc.RelationCondition(self.date)  # 加载固定工况计算类
            dict_res = relation.get_data()  # 预测的固定工况字典

            #固定策略与真实策略对应工况对比
            if len(dict_res) > 0: #如果有固定策略
                res_key_list = [i for i in dict_res.keys()]  # 固定工况字典的key列表
                bool_list = []  # 统计新工况与实际工况中的固定策略有没有变化
                for item in res_key_list:
                    if item in dict_actual.keys():  # 如果key在实际策略中也有
                        if list(dict_res[item]) == list(dict_actual[item]):  # 判断值是否相同
                            bool_list.append(True)
                        else:
                            bool_list.append(False)
                    else:  # 如果实际工况中没有
                        if sum(dict_res[item]) == 0:
                            bool_list.append(True)
                        else:
                            bool_list.append(False)
                bool_sign = True if False not in bool_list else False  # 布尔，表示新固定策略与旧固定策略是否相等True表示相等,相等情况下不合并固定与实际
            else:
                bool_sign = True

            #如果固定工况有变化,输出到temp表
            if bool_sign ==False:
                relation = sc.RelationCondition(self.date)  # 加载固定工况计算类
                con_dict = relation.get_data()  # 预测的固定工况字典
                con_list,_ = util.Device_util(graph_id,None,self.stgy_type).strategy_to_list_mean(con_dict, model_ene_id, self.date)
                to.to_temp(con_list)  # 将策略写入strategy_temp表
                logging.info("progress 3/7 --> to_temp down !")

            #有新策略的情况下
            if change_rate_ene > 0.1:  # 如果变化率大于0.1，更新策略模型
                #预测新策略,能量预测值写入pre_data_day,策略写入temp表
                sm = sa.Strategic_model(int(pre_ene["data"].values[0]), self.date,self.stgy_type)
                res = sm.strate_model()
                strat_new= sm.strate_format(res)  # 执行策略模型的主函数
                strat_list,strat_mean = util.Device_util(graph_id,None,self.stgy_type).strategy_to_list_mean(strat_new, model_ene_id, self.date)
                to.to_pre_data_day(self.dev_code_ene,pre_ene, int(model_ene_id))  # 预测能量需求且写入数据库pre_data_day
                to.to_temp(strat_list)  #写入temp库
                logging.info("progress 3/7 --> to_temp down !")
                #如果有升级工况,合并升级工况与新工况
                if status_upgrade == 1:
                    dict_upgrade = ul.strategy_to_dict(df_upgrade)
                    strat_item = ul.merge_upgrade(dict_upgrade, strat_mean)  #先合并新策略和升级策略
                    #有新固定策略,strat_item追加dict_res固定工况
                    if bool_sign == False: #如果固定策略有变化,将新的固定策略加到strat_item中
                        strat_item.update(dict_res)
                    #合并真实策略与strat_item
                    out_put = ul.merge_actual(dict_actual, strat_item,save = False) #原真实策略的未来值改为0
                #没有升级策略
                else:
                    #有新固定策略,新预测策略追加dict_res固定工况
                    if bool_sign == False:
                        strat_mean.update(dict_res)
                    #合并真实策略与strat_new合并
                    out_put = ul.merge_actual(dict_actual, strat_mean,save = False) #原真实策略的未来值改为0
            #没有新策略
            else:
                #有工况升级
                if status_upgrade == 1:
                    #升级工况df转换为dict格式
                    dict_upgrade = ul.strategy_to_dict(df_upgrade)
                    #有新固定策略
                    if bool_sign == False:
                        #先升级（合并真实和升级）,再merge_actual新固定策略
                        out_put = ul.merge_upgrade(dict_upgrade, dict_actual)
                        out_put = ul.merge_actual(out_put, dict_res,save = True)#原真实策略的未来值不变
                    else:#没有固定策略
                        out_put = ul.merge_upgrade(dict_upgrade, dict_actual)  #合并真实策略与工况升级策略
                #没有工况升级
                else:
                    #有新固定策略
                    if bool_sign == False:
                        #合并真实策略与新固定策略
                        out_put = ul.merge_actual(dict_actual, dict_res,save = True)#原真实策略的未来值不变
                    else:
                        out_put = dict_actual  # 真实策略,不变
                logging.info("progress 3/7 --> merge down !")
            #合并工况转list,输出到output
            output_data = ul.strategy_to_list_power(out_put, int(model_version_ene),self.date)  #得到最终执行工况写入output表的格式
            output_list = [item_[:-2] for item_ in output_data]
            to.to_output(output_list,self.date,self.stgy_type)  # 将合并后的策略写入output库
            #合并工况添加not_suggest工况,输出到history
            dict_not_suggest = relation.get_data_dispredict() #TODO
            dict_history = out_put.copy()
            dict_history.update(dict_not_suggest)
            to.to_history(self.stgy_type, dict_history, int(graph_id),self.date) # 将策略对应设备写入history_data表
            logging.info("progress 4/7 --> to_history down !")
        else:#当前图谱下没有预测工况
            out_put = {}
        #小时预测结果predict_dl 和predict_data表
        ene_hour, ele_hour, _ = self.pre_hour(out_put)  # 预测小时趋势 [ene, ele, stg]
        ene_data, ele_data = self.pre_hour_to_list(ene_hour, ele_hour)
        if change_rate_ene >=0.1:
            to.to_predict_data(ene_data,self.stgy_type)  # 写入能量predict_data
            logging.info("progress 5/7 --> to_predict_data down !")
        else:
            logging.info("progress 5/7 --> change_rate_ene <0.1 !")

        if change_rate_ele >=0.1:
            to.to_pre_data_day(self.dev_code_ele, pre_ele, int(model_ele_id))  # 预测能量需求且写入数据库pre_data_day
            logging.info("progress 6/7 --> to_pre_data_day down !")
            to.to_predict_dl(ele_data,self.stgy_type)  # 写入电量predict_dl
            logging.info("progress 7/7 --> to_predict_dl down !")
        else:
            logging.info("progress 6~7/7 --> change_rate_ele <0.1 !")
def main():
    """
    能源路由器每小时自动执行主函数,设每小时20分的定时任务
    """
    now = datetime.datetime.now()
    stgy_types = [1001, 1002, 1003]
    for stgy_type in stgy_types:
        try:
            logging.info("BEGIN on %s!",now)

            ra = auto(now,stgy_type)
            gl = load.Graph_load()
            graph_id = gl.put_databydate(datetime.datetime.now().strftime("%Y-%m-%d"))  #获取target对应日期的图谱ID
            work_end = gl.load_graph_worktime(int(graph_id))["end_work_time"]  # 图谱工作结束时间
            if now.hour == work_end:  # 如果现在时间是17时
                ra.hour_start()
            elif now.hour != (work_end-1):  # 如果现在是非17时
                ra.hour_non_start()
            logging.info("SUCCESS on %s!",now)
        except Exception as e:
            logging.exception("Exception occurred")

if __name__ == "__main__":
    main()
